/*
    索引模块（单例模式，懒汉方式）：构建正排索引、倒排索引
    正排索引：根据文档id，索引到文档
    倒排索引：根据关键字，索引到文档id
*/
#pragma once
#include <vector>
#include <unordered_map>
#include "util.hpp"

namespace ns_index
{
    struct DocInfo // 存储解析后结果(一个html对应一个元素)
    {
        std::string _title; //标题
        std::string _content; //内容
        std::string _url; //url
    };

    struct ForwardElem //正排索引元素
    {
        uint64_t _id; //id
        DocInfo _docinfo; //解析结果
    };

    struct WordFreqency //记录文档中关键词在标题和正文中出现的频次
    {
        WordFreqency():_title_cnt(0), _content_cnt(0)
        {}
        int _title_cnt; //标题中出现的次数
        int _content_cnt; //正文中出现的次数
    };

    struct InvertedElem //倒排索引元素
    {
        InvertedElem(): _weight(0)
        {}
        uint64_t _id; //id
        // std::string _word; //关键词(测试用)
        WordFreqency _wordfrecency; // 关键字在文档中频次
        int _weight; // 该关键词在文档的权重
    };

    typedef std::vector<InvertedElem> InvertedElemsList;

    class Index
    {
    private:
        Index(){}
        Index(const Index&) = delete;
        Index& operator=(const Index&) = delete;
    private:
        ForwardElem* buildForwardIndex(const std::string& s) //构建正排索引(一次处理一个文档)
        {
            // 切分s，提取title、content、url，构建ForwardElem
            std::vector<std::string> tokens;
            ns_util::StrUtil::splitStr(s, &tokens, "\3");
            ForwardElem e;
            e._docinfo._title = tokens[0];
            e._docinfo._content = tokens[1];
            e._docinfo._url = tokens[2];
            e._id = _forward_index.size();

            // 构建索引
            _forward_index.push_back(std::move(e));
            return &(_forward_index.back());
        }

        void buildInvertedIndex(const ForwardElem& forword_elem) //构建倒排索引(一次处理一个文档)
        {
            // 第一步：利用jieba分词并统计每个关键词出现的频次
            std::unordered_map<std::string, WordFreqency> frequencymap; // 统计所有关键词出现的频次

            // 1.1、title分词
            std::vector<std::string> title_tokens;
            ns_util::JiebaUtil::splitWord(forword_elem._docinfo._title, &title_tokens);

            // 1.2、统计title中关键词频次
            for(auto& word : title_tokens)
            {
                boost::to_lower(word); // 将大写转换成小写，构建索引不区分大小写，查找时也可以忽略大小写
                frequencymap[word]._title_cnt++;
            }

            // 1.3、content分词
            std::vector<std::string> content_tokens;
            ns_util::JiebaUtil::splitWord(forword_elem._docinfo._content, &content_tokens);

            // 1.4、统计content中关键词频次
            for(auto& word : content_tokens)
            {
                boost::to_lower(word);
                frequencymap[word]._content_cnt++;
            }

            // 第二步：根据关键词频次计算其所在文档的权重，并构建InveredElem元素
            // 关键词出现在标题的权重大于正文，权重体现着关键词和文档的相关性
            for(auto& wf : frequencymap) //遍历文档中出现的所有关键词
            {
                InvertedElem e;
                e._wordfrecency._title_cnt = wf.second._title_cnt;
                e._wordfrecency._content_cnt = wf.second._content_cnt;
                e._weight = wf.second._title_cnt * 10 + wf.second._content_cnt * 2; // 标题权重为10，正文权重为2
                //e._word = wf.first;
                e._id = forword_elem._id;
                _inverted_index[wf.first].push_back(std::move(e));
            }
        }
    public:
        static Index* getInstance() // 获取单例
        {
            if(_pindex == nullptr)
            {
                _mtx.lock();
                if(_pindex == nullptr)
                    _pindex = new Index;
                _mtx.unlock();
            }
            return _pindex;
        }

        // 根据存放解析结果的文件构建索引
        bool buildIndex(const std::string& parsed_result_filepath)
        {
            std::ifstream in(parsed_result_filepath, std::ios::in | std::ios::binary);
            if(!in.is_open())
            {
                LOG(FATAL, "文件打开失败\n");
                return false;
            }

            std::string number_str, line;
            getline(in, number_str); //获取第一行总数（支持进度条）
            // double total = stod(number_str);
            // int cnt = 0;
            while(getline(in, line)) //一次读取一行，一行就是一个解析后的html，每次处理一个文件
            {
                // ns_util::ProcessbarUtil::processbar((cnt/total)*100); // 进度条（暂时取消）
                ForwardElem* pforwardelem = buildForwardIndex(line); //构建正排索引
                if(pforwardelem == nullptr)
                {
                    LOG(FATAL, "构建正排索引失败\n");
                    continue;
                }
                buildInvertedIndex(*pforwardelem); //构建倒排索引
                //cnt++;
            }

            // LOG(DEBUG, "正排索引: %d\n", _forward_index.size());
            // LOG(DEBUG, "倒排索引哈希桶个数: %d\n", _inverted_index.size());

            in.close();
            return true;
        }

        const ForwardElem* forwardIndex(uint64_t id) //根据文档id索引到文档
        {
            return &_forward_index[(int)id];
        }

        const InvertedElemsList* invertedIndex(const std::string& word) //根据关键字索引到文档id
        {
            auto it = _inverted_index.find(word);
            if(it == _inverted_index.end())
                return nullptr;
            else
                return &it->second;
        }

    private:
        std::vector<ForwardElem> _forward_index; //正排索引
        std::unordered_map<std::string, InvertedElemsList> _inverted_index; //倒排索引（一个关键字索引到多个文档）

        // 单例模式
        static Index* _pindex;
        static std::mutex _mtx; 
    };
    Index* Index::_pindex = nullptr;
    std::mutex Index::_mtx;
}